package com.hashcoding.eyes.module.manager;

import android.content.Context;
import android.location.LocationManager;
import android.os.Handler;
import android.text.TextUtils;

import com.amap.api.location.AMapLocation;
import com.amap.api.location.AMapLocationClient;
import com.amap.api.location.AMapLocationClientOption;
import com.amap.api.location.AMapLocationListener;
import com.hashcoding.eyes.BuildConfig;
import com.hashcoding.eyes.EyesApplication;
import com.hashcoding.eyes.data.Event.LocateEvent;
import com.hashcoding.eyes.module.utils.GsonUtil;
import com.hashcoding.eyes.module.utils.LogUtil;
import com.hashcoding.eyes.module.utils.PreferenceUtil;

/**
 * 高德定位
 *
 * @author markmjw
 * @date 2016-01-18
 */
public final class LocateManager implements AMapLocationListener {
    private static final String LAST_LOCATION = "last_location";

    /** 定位成功 */
    public static final int STATUS_SUCCESS = 0x01;
    /** 定位中 */
    public static final int STATUS_LOCATING = 0x02;
    /** 未知 */
    public static final int STATUS_UNKNOWN = 0x03;
    /** 定位失败 */
    public static final int STATUS_FAILED = 0x04;
    /** 定位超时 */
    public static final int STATUS_TIMEOUT = 0x05;

    private static final String TAG = "LocateManager";

    /** 时间间隔10分钟 */
    private static final long INTERVAL_TIME = 1000L * 60 * 10;

    /** 默认的超时时间 */
    private static final long DEFAULT_TIMEOUT = 10000;

    private static LocateManager sInstance;
    private static AMapLocationClient sLocationClient;

    // 定位状态
    private int mStatus = STATUS_UNKNOWN;
    // 上次成功定位时间戳
    private long mLastLocationTime;
    // 定位信息
    private AMapLocation mLocation;

    private Handler mHandler;

    private TimeoutRunnable mTimeoutRunnable = new TimeoutRunnable();

    public synchronized static LocateManager getInstance() {
        if (sInstance == null) {
            sInstance = new LocateManager();
        }
        return sInstance;
    }

    private LocateManager() {
        Context context = EyesApplication.gContext;
        mHandler = new Handler(context.getMainLooper());
        mLastLocationTime = 0L;
        readLastLocation();

        AMapLocationClientOption option = new AMapLocationClientOption();
        // 需要显示地址信息
        option.setNeedAddress(true);
        // 设置是否优先返回GPS定位结果，如果30秒内GPS没有返回定位结果则进行网络定位
        // 注意：只有在高精度模式下的单次定位有效，其他方式无效
        option.setGpsFirst(false);
        // 单次定位
        option.setOnceLocation(true);
        // 设置发送定位请求的时间间隔,最小值为2000，如果小于2000，按照2000算
//        option.setInterval(INTERVAL_TIME);
        // 设置定位模式为高精度模式
        option.setLocationMode(AMapLocationClientOption.AMapLocationMode.Hight_Accuracy);

        sLocationClient = new AMapLocationClient(context);
        sLocationClient.setLocationOption(option);
        // 设置定位监听
        sLocationClient.setLocationListener(this);
    }

    @SuppressWarnings("deprecation")
    private void readLastLocation() {
        String data = PreferenceUtil.getString(LAST_LOCATION);
        if (!TextUtils.isEmpty(data)) {
            try {
                Location loc = GsonUtil.fromJson(data, Location.class);
                if (null != loc) {
                    mLocation = new AMapLocation(loc.provider);
                    mLocation.setAdCode(loc.adCode);
                    mLocation.setAddress(loc.address);
                    mLocation.setCity(loc.city);
                    mLocation.setCityCode(loc.cityCode);
                    mLocation.setCountry(loc.country);
                    mLocation.setDistrict(loc.district);
                    mLocation.setErrorCode(loc.errorCode);
                    mLocation.setErrorInfo(loc.errorInfo);
                    mLocation.setLatitude(loc.latitude);
                    mLocation.setLatitude(loc.longitude);
                    mLocation.setLocationType(loc.locationType);
                    mLocation.setPoiName(loc.poiName);
                    mLocation.setProvince(loc.province);
                    mLocation.setSpeed(loc.speed);
                    mLocation.setStreet(loc.street);
                    mLocation.setSatellites(loc.satellites);
                    mLocation.setLocationDetail(loc.locationDetail);
                    mLocation.setNumber(loc.number);
                    mLocation.setAccuracy(loc.accuracy);
                    mLocation.setBearing(loc.bearing);
                    mLocation.setAltitude(loc.altitude);
                    mLocation.setRoad(loc.road);
                }
            } catch (Exception e) {
                // ignore
            }
        }
    }

    /**
     * 开始定位
     */
    public void startLocate() {
        startLocate(DEFAULT_TIMEOUT);
    }

    /**
     * 开始定位
     *
     * @param timeout 超时时间
     */
    public void startLocate(long timeout) {
        if (needLocate()) {
            mStatus = STATUS_LOCATING;
            EventBusManager.post(new LocateEvent(LocateEvent.CODE_LOCATING));
            sLocationClient.startLocation();
            postTimeoutRunnable(timeout);

        } else {
            switch (mStatus) {
                case STATUS_LOCATING:
                    EventBusManager.post(new LocateEvent(LocateEvent.CODE_LOCATING));
                    postTimeoutRunnable(timeout);
                    break;

                case STATUS_SUCCESS:
                    EventBusManager.post(new LocateEvent(LocateEvent.CODE_SUCCESS));
                    break;
            }
        }
    }

    /**
     * 停止定位
     */
    public void stopLocate() {
        sLocationClient.stopLocation();
        mHandler.removeCallbacks(mTimeoutRunnable);
    }

    /**
     * 销毁定位
     */
    public void destroy() {
        sInstance = null;
        if (null != sLocationClient) {
            // 在Activity的onDestroy中一定要执行AMapLocationClient的onDestroy
            sLocationClient.onDestroy();
            sLocationClient = null;
        }
    }

    /**
     * 获取定位数据
     *
     * @return
     */
    public AMapLocation getLocation() {
        return mLocation;
    }

    /**
     * 获取定位状态
     *
     * @return
     */
    public int getStatus() {
        return mStatus;
    }

    /**
     * 是否需要定位：定位时间过大；定位状态为未知、失败或超时
     *
     * @return
     */
    private boolean needLocate() {
        return System.currentTimeMillis() - mLastLocationTime >= INTERVAL_TIME
                || mStatus == STATUS_FAILED
                || mStatus == STATUS_TIMEOUT
                || mStatus == STATUS_UNKNOWN;
    }

    private void postTimeoutRunnable(long timeout) {
        mHandler.removeCallbacks(mTimeoutRunnable);
        mHandler.postDelayed(mTimeoutRunnable, timeout);
    }

    private void removeTimeoutRunnable() {
        mHandler.removeCallbacks(mTimeoutRunnable);
    }

    @Override
    public void onLocationChanged(AMapLocation loc) {
        removeTimeoutRunnable();
        stopLocate();

        AMapLocation location = loc;
        if (location == null) {
            location = sLocationClient.getLastKnownLocation();
        }

        if (location != null) {
            if (location.getErrorCode() == 0) {
                // 打印定位成功信息
                logSuccess(location);

                mLocation = location;
                mLastLocationTime = System.currentTimeMillis();
                mStatus = STATUS_SUCCESS;
                PreferenceUtil.saveString(LAST_LOCATION, mLocation.toStr());
                EventBusManager.post(new LocateEvent(LocateEvent.CODE_SUCCESS));

            } else {
                mStatus = STATUS_FAILED;
                // 定位失败
                logFailed(location);
                EventBusManager.post(new LocateEvent(LocateEvent.CODE_FAILED));
            }
        } else {
            mStatus = STATUS_FAILED;
            // 定位失败
            logFailed(null);
            EventBusManager.post(new LocateEvent(LocateEvent.CODE_FAILED));
        }
    }

    private void logSuccess(AMapLocation location) {
        if (!BuildConfig.DEBUG) return;

        StringBuilder sb = new StringBuilder();
        sb.append("定位成功.").append("\n");
        sb.append("定位类型: ").append(location.getLocationType()).append("\n");
        sb.append("经度: ").append(location.getLongitude()).append("\n");
        sb.append("纬度: ").append(location.getLatitude()).append("\n");
        sb.append("精度: ").append(location.getAccuracy()).append("米").append("\n");
        sb.append("提供者: ").append(location.getProvider()).append("\n");

        if (LocationManager.GPS_PROVIDER.equalsIgnoreCase(location.getProvider())) {
            // 以下信息只有提供者是GPS时才会有
            sb.append("速度: ").append(location.getSpeed()).append("米/秒").append("\n");
            sb.append("角度: ").append(location.getBearing()).append("\n");
            // 获取当前提供定位服务的卫星个数
            sb.append("星数: ").append(location.getExtras().getInt("satellites", 0));
        } else {
            // 提供者是GPS时是没有以下信息的
            sb.append("国家: ").append(location.getCountry()).append("\n");
            sb.append("省: ").append(location.getProvince()).append("\n");
            sb.append("市: ").append(location.getCity()).append("\n");
            sb.append("城市编码: ").append(location.getCityCode()).append("\n");
            sb.append("区: ").append(location.getDistrict()).append("\n");
            sb.append("区域码: ").append(location.getAdCode()).append("\n");
            sb.append("地址: ").append(location.getAddress());
        }
        LogUtil.i(TAG, sb.toString());
    }

    private void logFailed(AMapLocation location) {
        if (!BuildConfig.DEBUG) return;
        if (null != location) {
            String sb = "定位失败." + "\n" +
                    "错误码:" + location.getErrorCode() + "\n" +
                    "错误信息:" + location.getErrorInfo() + "\n" +
                    "错误描述:" + location.getLocationDetail();
            LogUtil.e(TAG, sb);
        } else {
            LogUtil.e(TAG, "定位失败，未获取到定位信息！");
        }
    }

    private class TimeoutRunnable implements Runnable {

        @Override
        public void run() {
            mStatus = STATUS_TIMEOUT;
            stopLocate();
            EventBusManager.post(new LocateEvent(LocateEvent.CODE_LOCATE_TIMEOUT));
        }
    }

    private class Location {
        public String country;
        public String province;
        public String city;
        public String cityCode;
        public String district;
        public String adCode;
        public String address;
        public String road;
        public String street;
        public String number;
        public String poiName;
        public int errorCode;
        public String errorInfo;
        public String locationDetail;
        public int altitude;
        public int bearing;
        public int speed;
        public int satellites;
        public long time;
        public int locationType;
        public int accuracy;
        public double latitude;
        public double longitude;
        public String provider;
    }
}
